package br.sed.theenemy.business.services;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.mail.MessagingException;
import javax.mail.internet.AddressException;

import br.sed.theenemy.business.exception.DBException;
import br.sed.theenemy.business.exception.HashNotValidException;
import br.sed.theenemy.business.exception.InvalidActionException;
import br.sed.theenemy.business.exception.InvalidUniqueBeanException;
import br.sed.theenemy.business.exception.InvalidUserException;
import br.sed.theenemy.business.utils.Utils;
import br.sed.theenemy.model.bean.Action;
import br.sed.theenemy.model.bean.Contact;
import br.sed.theenemy.model.bean.User;
import br.sed.theenemy.model.bean.UserRequest;
import br.sed.theenemy.model.dao.ActionDao;
import br.sed.theenemy.model.dao.ContactDao;
import br.sed.theenemy.model.dao.UserDao;
import br.sed.theenemy.model.dao.UserRequestDao;
import br.sed.theenemy.presentation.EnemyConstants;
import br.sed.theenemy.presentation.vo.ActionVO;
import br.sed.theenemy.presentation.vo.SubActionVO;
import br.sed.theenemy.presentation.vo.UserVO;

/**
 * 
 * @author JulioCB
 * @email jcbjulio@gmail.com
 *
 */
public class Services {

	private static Services services;
	
	//UserRequest
	public void createUserRequest(UserRequest userRequest, String hostName) throws NoSuchAlgorithmException, UnsupportedEncodingException, AddressException, MessagingException, DBException, InvalidUniqueBeanException{
		UserRequestDao userRequestDao = (UserRequestDao) UserRequestDao.getInstance();
		UserDao userDao = (UserDao) UserDao.getInstance();
		Notifier mail = new Notifier();
		
		try {
            userDao.findUserByEmail(userRequest.getEmail());
        } catch (NumberFormatException e) {
            throw new DBException("Number Format Error", e);
        } catch (SQLException e) {
            throw new DBException("SQL Error", e);
        } catch (ParseException e) {
            throw new DBException("Parse Error", e);
        }catch (InvalidUserException e) {
            userRequestDao.invalidateOldHash(userRequest.getEmail());
            userRequestDao.create(userRequest);
            mail.sendRequestUserMail(userRequest.getEmail(), userRequest.getHash(), hostName);
            return;
        }
        throw new InvalidUniqueBeanException("Usuario já está cadastrado.");
	}
	
	//UserRequest - User
	public void activateUser(String hash) throws DBException, HashNotValidException {
		UserRequestDao userRequestDao = (UserRequestDao) UserRequestDao.getInstance();
		UserDao userDao = (UserDao) UserDao.getInstance();
		
		UserRequest userRequest;
		try {
			userRequest = userRequestDao.getUserRequestbyHash(hash);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
		if (userRequest == null){
			throw new HashNotValidException();
		}
		

		User user = new User(userRequest);
		user = userDao.create(user);
		userRequestDao.invalidateOldHash(userRequest.getEmail());
		createActionRegister(user.getId());
	}
	
	//User
	public List<User> findUserByPieceOfName(String pName, Integer page, Integer max) throws DBException{
		UserDao userDao = (UserDao) UserDao.getInstance();
		Integer start = Utils.getStartFromPageMax(page, max);
		try {
			return userDao.findByPieceOfName(pName, start, max);
		}catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//User
	public Integer countUserByPieceOfName(String pName) throws DBException {
		UserDao userDao = (UserDao) UserDao.getInstance();
		try {
			return userDao.countByPieceOfName(pName);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		}
	}
	
	//User
	public List<User> findUserRandom(Integer max) throws DBException{
		UserDao userDao = (UserDao) UserDao.getInstance();
		try {
			return userDao.findRandom(max);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	} 
	
	//User
	public User login(String email, String password) throws InvalidUserException, InvalidUniqueBeanException, DBException{
		UserDao userDao = (UserDao) UserDao.getInstance();
		User user;
		try {
			user = userDao.findUserByEmail(email);
		} catch (NumberFormatException e) {
			throw new DBException("Number Format Error", e);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
		String passwordMD5 = Utils.MD5(password);
		if(user.getPass().equals(passwordMD5)){
			return user;
		}			
		throw new InvalidUserException("Invalid User Exception");
	}
	
	//User
	public User findUserById(Long userId) throws InvalidUniqueBeanException, DBException, InvalidUserException{
		UserDao userDao = (UserDao) UserDao.getInstance();
		try {
			return userDao.findUserById(userId);
		} catch (NumberFormatException e) {
			throw new DBException("Number Format Error", e);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//User
	public void updateUser(Long userId, String firstName, String lastName, String gender, Date bornDate) throws DBException{
		UserDao userDao = (UserDao) UserDao.getInstance();
		
		User user = new User();
		user.setId(userId);
		user.setFirstName(firstName);
		user.setLastName(lastName);
		user.setGender(gender);
		user.setBornDate(bornDate);
		
		userDao.update(user);
	}
	
	//User
	public void changePassword(Long id, String password) throws DBException{
		UserDao userDao = (UserDao) UserDao.getInstance();
		
		User user = new User();
		user.setId(id);
		user.setPass(password);
		
		userDao.changePassword(user);
	}
	
	//User
    public void changeImage(Long id, String imgUrl) throws DBException{
        UserDao userDao = (UserDao) UserDao.getInstance();
        
        User user = new User();
        user.setId(id);
        user.setImgUrl(imgUrl);
        
        userDao.changeImage(user);
    }
	
	//Contacts
	public void createContact(Long sourceId, Long targetId) throws DBException, InvalidUniqueBeanException, InvalidUserException{
		ContactDao contactDao = (ContactDao) ContactDao.getInstance();
		UserDao userDao = (UserDao) UserDao.getInstance();
		
		User targetUser;
		try {
			targetUser = userDao.findUserById(targetId);
		} catch (NumberFormatException e) {
			throw new DBException("Number Format Error", e);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
		
		Contact contact = new Contact();
		contact.setSourceId(sourceId);
		contact.setTargetId(targetUser.getId());
		contactDao.create(contact);
	}
	
	//User from Contact
	public List<User> findContactsBySourceId(Long sourceId, Integer page, Integer max) throws DBException{
		ContactDao contactDao = (ContactDao) ContactDao.getInstance();
		Integer start = Utils.getStartFromPageMax(page, max);
		try {
			return contactDao.findUsersBySourceId(sourceId, start, max);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//User from Contact
	public List<User> findAllContactsBySourceId(Long sourceId) throws DBException{
		ContactDao contactDao = (ContactDao) ContactDao.getInstance();
		try {
			return contactDao.findAllUsersBySourceId(sourceId);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//Contact
	public Integer countContactsBySourceId(Long sourceId) throws DBException{
		ContactDao contactDao = (ContactDao) ContactDao.getInstance();
		try {
			return contactDao.countBySourceId(sourceId);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		}
	}
	
	//Contact
	public void deleteContact(Long sourceId, Long targetId) throws DBException{
		ContactDao contactDao = (ContactDao) ContactDao.getInstance();
		contactDao.delete(sourceId, targetId);
	}
	
	//Action
	public void createAction(Long typeId, Long userId, Long actionIdRel, String comment) throws DBException{
		ActionDao actionDao = (ActionDao) ActionDao.getInstance();
		Action action = new Action();
		action.setTypeId(typeId);
		action.setUserId(userId);
		action.setActionIdRel(actionIdRel);
		action.setComment(comment);
		actionDao.create(action);
	}
	
	//Action
	public void createActionComment(Long userId, String comment) throws DBException{
		createAction(EnemyConstants.COMMENT, userId, EnemyConstants.NULL_REL, comment);
	}
	
	//Action
	public void createActionAttack(Long userId, Long actionIdRel, String comment) throws DBException, InvalidUniqueBeanException, InvalidUserException, InvalidActionException{

            if(findActionById(actionIdRel)!=null){
                createAction(EnemyConstants.ATTACK, userId, actionIdRel, comment);
            }else{
                throw new InvalidActionException("Action not found");
            }

	}
	
	//Action
	public void createActionDefend(Long userId, Long actionIdRel, String comment) throws DBException, InvalidUniqueBeanException, InvalidUserException, InvalidActionException{
	    ActionDao actionDao = (ActionDao) ActionDao.getInstance();
	    actionDao.updateUserSide(userId, actionIdRel, EnemyConstants.DEFEND);
	    
        if(findActionById(actionIdRel)!=null){
            createAction(EnemyConstants.DEFEND, userId, actionIdRel, comment);
    	}else{
            throw new InvalidActionException("Action not found");
        }
	}
	
	//Action
	public void createActionSupport(Long userId, Long actionIdRel, String comment) throws DBException, InvalidUniqueBeanException, InvalidUserException, InvalidActionException{
	    ActionDao actionDao = (ActionDao) ActionDao.getInstance();
	    actionDao.updateUserSide(userId, actionIdRel, EnemyConstants.SUPPORT);
	    
        if(findActionById(actionIdRel)!=null){
            createAction(EnemyConstants.SUPPORT, userId, actionIdRel, comment);
        }else{
            throw new InvalidActionException("Action not found");
        }
	}
	
	//Action
    public void createActionAttackUser(Long userId, Long userTargetId, String comment) throws DBException, InvalidUniqueBeanException, InvalidUserException{
        //TODO buscar qual a action de criacao deste usuario, e atacar esta action
        UserDao userDao = (UserDao) UserDao.getInstance();
        ActionDao actionDao = (ActionDao) ActionDao.getInstance();
        
        User user;
        try {
            user = userDao.findUserById(userTargetId);
        } catch (NumberFormatException e) {
            throw new DBException("Number Format Error", e);
        } catch (SQLException e) {
            throw new DBException("SQL Error", e);
        } catch (ParseException e) {
            throw new DBException("Parse Error", e);
        }
        
        Action action;
        try {
            action = actionDao.findUserCreation(user.getId());
        } catch (NumberFormatException e) {
            throw new DBException("Number Format Error", e);
        } catch (SQLException e) {
            throw new DBException("SQL Error", e);
        } catch (ParseException e) {
            throw new DBException("Parse Error", e);
        }
        
        if(action!=null){
            createAction(EnemyConstants.ATTACK, userId, action.getId(), comment);
        }else{
            throw new InvalidUserException("Action de criação não encontrada");
        }
    }
	
	//Action
	public void createActionRegister(Long userId) throws DBException{
		createAction(EnemyConstants.CREATE, userId, EnemyConstants.NULL_REL, "Acabei de me cadastrar");
	}
	
	//Action
	public Action findActionById(Long actionId) throws DBException, InvalidUniqueBeanException, InvalidUserException{
	    ActionDao actionDao = (ActionDao) ActionDao.getInstance();
	    try {
            return actionDao.findById(actionId);
        } catch (NumberFormatException e) {
            throw new DBException("Number Format Error", e);
        } catch (SQLException e) {
            throw new DBException("SQL Error", e);
        } catch (ParseException e) {
            throw new DBException("Parse Error", e);
        }
	}
	
	//Action
	public ActionVO findActionStreamById(Long id) throws InvalidUniqueBeanException, DBException, InvalidUserException{
		ActionDao actionDao = (ActionDao) ActionDao.getInstance();
		try {
			return actionDao.findActionStreamById(id);
		} catch (NumberFormatException e) {
			throw new DBException("Number Format Error", e);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//Action
	public Action findLastCommentByUserId(Long userId) throws DBException{
		ActionDao actionDao = (ActionDao) ActionDao.getInstance();
		try {
			return actionDao.findLastCommentByUserId(userId);
		} catch (NumberFormatException e) {
			throw new DBException("Number Format Error", e);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//Action
	public List<Action> findSubActionsByActionId(Long actionId) throws DBException{
		ActionDao actionDao = (ActionDao) ActionDao.getInstance();
		try {
			return actionDao.findAllByActionId(actionId);
		} catch (NumberFormatException e) {
			throw new DBException("Number Format Error", e);
		} catch (SQLException e) {
			throw new DBException("SQL Error", e);
		} catch (ParseException e) {
			throw new DBException("Parse Error", e);
		}
	}
	
	//Action
	public List<ActionVO> findActionStream(Long userId) throws DBException, InvalidUniqueBeanException, InvalidUserException{
	    ActionDao actionDao = (ActionDao) ActionDao.getInstance();
        List<ActionVO> actionListVO;
        try {
            actionListVO = actionDao.findActionStream(userId);
        } catch (SQLException e) {
            throw new DBException("SQL Error", e);
        }
	    return fillActionStream(actionListVO);
	}
	
	//Action
    public List<ActionVO> findAllActionStream(Long userId) throws DBException, InvalidUniqueBeanException, InvalidUserException{
        ActionDao actionDao = (ActionDao) ActionDao.getInstance();
        List<ActionVO> actionListVO;
        try {
            actionListVO = actionDao.findAllActionStream(userId);
        } catch (SQLException e) {
            throw new DBException("SQL Error", e);
        }
        return fillActionStream(actionListVO);
    }
	
	//Action
	public List<ActionVO> fillActionStream(List<ActionVO> actionListVO) throws DBException, InvalidUniqueBeanException, InvalidUserException{
	    
	    List<ActionVO> fullActionListVO = new ArrayList<ActionVO>();
	    Map<Long, Long> mapActionId = new HashMap<Long, Long>();
	    for(ActionVO actionVO: actionListVO){
	        ActionVO fullActionVO = new ActionVO();
	        if(actionVO.getActionType()==EnemyConstants.ATTACK || actionVO.getActionType()==EnemyConstants.SUPPORT || actionVO.getActionType()==EnemyConstants.DEFEND){
	            if(actionVO.getActionType()==EnemyConstants.SUPPORT || actionVO.getActionType()==EnemyConstants.DEFEND){
	                actionVO = findActionStreamById(actionVO.getActionRelId());
	            }
	            if(mapActionId.get(actionVO.getId())==null){
    	            mapActionId.put(actionVO.getId(),actionVO.getId());
    	            fullActionVO.setId(actionVO.getId());
    	            fullActionVO.setActionType(actionVO.getActionType());
    	            fullActionVO.setActionRelId(actionVO.getActionRelId());
    	            fullActionVO.setUserAttack(actionVO.getUserAttack());
    	            fullActionVO.setUserDefend(actionVO.getUserDefend());
    	            fullActionVO.setComment(actionVO.getComment());
    	            
    	            fullActionVO.setAttack(true);
    	            
                    //find de subActions of this action
                    List<Action> subActionList;
                    subActionList = findSubActionsByActionId(fullActionVO.getId());
                    List<SubActionVO> subActionListVO = new ArrayList<SubActionVO>();
                    int countAllyAttack = 0;
                    int countAllyDefend = 0;
                    Map<Long, Long> mapAllyAttack = new HashMap<Long, Long>();
                    Map<Long, Long> mapAllyDefend = new HashMap<Long, Long>();
                    for(Action subAction: subActionList){
                        if(subAction.getTypeId()!=EnemyConstants.ATTACK){
                            SubActionVO subActionVO = new SubActionVO();
                            if(subAction.getTypeId()==EnemyConstants.DEFEND){
                                if(mapAllyDefend.get(subAction.getUserId())==null){
                                    mapAllyDefend.put(subAction.getUserId(), subAction.getUserId());
                                    countAllyDefend++;
                                }
                                subActionVO.setAttack(false);
                            }else if(subAction.getTypeId()==EnemyConstants.SUPPORT){
                                if(mapAllyAttack.get(subAction.getUserId())==null){
                                    mapAllyAttack.put(subAction.getUserId(), subAction.getUserId());
                                    countAllyAttack++;
                                }
                                subActionVO.setAttack(true);
                            }
                            subActionVO.setId(subAction.getId());
                            subActionVO.setComment(subAction.getComment());
                            
                            //FIXME remover quando substituir a query
                            User user;
                            try {
                                user = findUserById(subAction.getUserId());
                            } catch (InvalidUniqueBeanException e) {
                                //TODO
                                user = new User();
                            }
                            UserVO userVO = new UserVO(user);
                            subActionVO.setUser(userVO);
                            //FIXME remover quando substituir a query
                            
                            subActionListVO.add(subActionVO);
                        }
                    }
                    fullActionVO.setSubActionList(subActionListVO);
                    fullActionVO.setAllyAttack(countAllyAttack);
                    fullActionVO.setAllyDefend(countAllyDefend);
                    fullActionVO.setCountSubAction(subActionListVO.size());
                    
                    fullActionListVO.add(fullActionVO);
	            }
	        }else{
	            fullActionVO.setId(actionVO.getId());
	            fullActionVO.setActionType(actionVO.getActionType());
	            fullActionVO.setUserAttack(actionVO.getUserAttack());
	            fullActionVO.setComment(actionVO.getComment());
	            actionVO.setAttack(false);
	        
	            fullActionListVO.add(fullActionVO);
	        }
	    }
	    return fullActionListVO;
	}
	
	public static Services getInstance(){
		
		// Verifica se ja tem instancia
		if ( services == null ){			
			services = new Services();			
		}
		
		return services;
	}
}
